import Context from "../Context";
import Config from "../../utils/Config";
import EditingController from "../../engine/controller/EditingController";
import PluginCollection from "../PluginCollection";
import CommandCollection from "../CommandCollection";
import DataController from "../../engine/controller/DataController";
import Conversion from "../../engine/conversion/Conversion";
import Model from "../../engine/model/Model";
import EditingKeystrokeHandler from "../EditingKeystrokeHandler";

import ObservableMixin from "../../utils/ObservableMixin";
import mix from "../../utils/mix";
import CKEditorError from "../../utils/CkEditorError";
import { StylesProcessor } from "../../engine/view/stylesmap";

class Editor {
    _context: object;
    config: Config;
    plugins: PluginCollection;
    local: object;
    t: object;
    commands: CommandCollection;
    editing: EditingController;
    model: Model;
    keystrokes: EditingKeystrokeHandler;
    conversion: Conversion;

    constructor(config: Config) {
        /**
         * The editor context.
         * When it is not provided through the configuration, the editor creates it.
         *
         * @protected
         * @type {module:core/context~Context}
         */
        this._context =
            config.context || new Context({ language: config.language });
        this._context._addEditor(this, !config.context);

        // Clone the plugins to make sure that the plugin array will not be shared
        // between editors and make the watchdog feature work correctly.
        const availablePlugins = Array.from(
            this.constructor.builtinPlugins || []
        );

        /**
         * Stores all configurations specific to this editor instance.
         *
         *		editor.config.get( 'image.toolbar' );
         *		// -> [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
         *
         * @readonly
         * @member {module:utils/config~Config}
         */
        this.config = new Config(config, this.constructor.defaultConfig);
        this.config.define("plugins", availablePlugins);
        this.config.define(this._context._getEditorConfig());

        /**
         * The plugins loaded and in use by this editor instance.
         *
         *		editor.plugins.get( 'Clipboard' ); // -> An instance of the clipboard plugin.
         *
         * @readonly
         * @member {module:core/plugincollection~PluginCollection}
         */
        this.plugins = new PluginCollection(
            this,
            availablePlugins,
            this._context.plugins
        );

        /**
         * The locale instance.
         *
         * @readonly
         * @type {module:utils/locale~Locale}
         */
        this.locale = this._context.locale;

        /**
         * Shorthand for {@link module:utils/locale~Locale#t}.
         *
         * @see module:utils/locale~Locale#t
         * @method #t
         */
        this.t = this.locale.t;

        /**
         * Commands registered to the editor.
         *
         * Use the shorthand {@link #execute `editor.execute()`} method to execute commands:
         *
         *		// Execute the bold command:
         *		editor.execute( 'bold' );
         *
         *		// Check the state of the bold command:
         *		editor.commands.get( 'bold' ).value;
         *
         * @readonly
         * @member {module:core/commandcollection~CommandCollection}
         */
        this.commands = new CommandCollection();

        /**
         * Indicates the editor life-cycle state.
         *
         * The editor is in one of the following states:
         *
         * * `initializing` &ndash; During the editor initialization (before
         * {@link module:core/editor/editor~Editor.create `Editor.create()`}) finished its job.
         * * `ready` &ndash; After the promise returned by the {@link module:core/editor/editor~Editor.create `Editor.create()`}
         * method is resolved.
         * * `destroyed` &ndash; Once the {@link #destroy `editor.destroy()`} method was called.
         *
         * @observable
         * @member {'initializing'|'ready'|'destroyed'} #state
         */
        this.set("state", "initializing");
        this.once("ready", () => (this.state = "ready"), { priority: "high" });
        this.once("destroy", () => (this.state = "destroyed"), {
            priority: "high",
        });

        /**
         * Defines whether this editor is in read-only mode.
         *
         * In read-only mode the editor {@link #commands commands} are disabled so it is not possible
         * to modify the document by using them. Also, the editable element(s) become non-editable.
         *
         * In order to make the editor read-only, you can set this value directly:
         *
         *		editor.isReadOnly = true;
         *
         * @observable
         * @member {Boolean} #isReadOnly
         */
        this.set("isReadOnly", false);

        /**
         * The editor's model.
         *
         * The central point of the editor's abstract data model.
         *
         * @readonly
         * @member {module:engine/model/model~Model}
         */
        this.model = new Model();

        const stylesProcessor = new StylesProcessor();

        /**
         * The {@link module:engine/controller/datacontroller~DataController data controller}.
         * Used e.g. for setting and retrieving the editor data.
         *
         * @readonly
         * @member {module:engine/controller/datacontroller~DataController}
         */
        this.data = new DataController(this.model, stylesProcessor);

        /**
         * The {@link module:engine/controller/editingcontroller~EditingController editing controller}.
         * Controls user input and rendering the content for editing.
         *
         * @readonly
         * @member {module:engine/controller/editingcontroller~EditingController}
         */
        this.editing = new EditingController(this.model, stylesProcessor);
        this.editing.view.document.bind("isReadOnly").to(this);

        /**
         * Conversion manager through which you can register model-to-view and view-to-model converters.
         *
         * See the {@link module:engine/conversion/conversion~Conversion} documentation to learn how to add converters.
         *
         * @readonly
         * @member {module:engine/conversion/conversion~Conversion}
         */
        this.conversion = new Conversion(
            [this.editing.downcastDispatcher, this.data.downcastDispatcher],
            this.data.upcastDispatcher
        );
        this.conversion.addAlias("dataDowncast", this.data.downcastDispatcher);
        this.conversion.addAlias(
            "editingDowncast",
            this.editing.downcastDispatcher
        );

        this.keystrokes = new EditingKeystrokeHandler(this);
        this.keystrokes.listenTo(this.editing.view.document);
    }
}
export default Editor;
